C++ 50-55 对象模型分析
50 C++对象模型分析(上)
回归本质
-
class是一种特殊的struct
- 在内存中class依旧可以看作变量的集合
- class与struct遵循相同的内存对齐规则
- class中的成员函数与成员变量是分开存放的
- 每个对象有独立的成员变量
- 所有对象共享类中的成员函数
-
值得思考的问题
class A {
int i;
int j;
char c;
double d;
public:
void print() {
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B {
int i;
int j;
char c;
double d;
};
sizeof(A) = ? //20 bytes
sizeof(B) = ? //20 bytes
编程实验
-
对象内存布局初探
#include <iostream>
#include <string>
using namespace std;
class A {
int i;
int j;
char c;
double d;
public:
void print() {
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B {
int i;
int j;
char c;
double d;
};
int main() {
A a;
cout << "sizeof(A) = " << sizeof(A) << endl; // 20 bytes
cout << "sizeof(a) = " << sizeof(a) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl; // 20 bytes
a.print();
B* p = reinterpret_cast<B*>(&a);
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3;
a.print();
p->i = 100;
p->j = 200;
p->c = 'C';
p->d = 3.14;
a.print();
return 0;
}
C++对象模型分析
- 运行时的对象退化为结构体的形式
- 所有成员变量在内存中依次排布
- 成员变量间可能存在内存空隙
- 可以通过内存地址直接访问成员变量
- 访问权限关键字在运行时失效
- 类中的成员函数位于代码段中
- 调用成员函数时对象地址作为参数隐式传递
- 成员函数通过对象地址访问成员变量
- C++语法规则隐藏了对象地址的传递过程
编程实验
-
对象本质分析
//main.cpp
#include <iostream>
#include <string>
using namespace std;
class Demo {
int mi;
int mj;
public:
Demo(int i, int j) {
mi = i;
mj = j;
}
int getI() { return mi;}
int getJ() { return mj;}
int add(int value){ return mi + mj + value;}
};
int main() {
Demo d(1, 2);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "d.getI() = " << d.getI() << endl;
cout << "d.getJ() = " << d.getJ() << endl;
cout << "d.add(3) = " << d.add(3) << endl;
return 0;
}//.h
#ifndef _50_2_H_
#define _50_2_H_
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
#endif//.c
#include "50-2.h"
#include "malloc.h"
struct ClassDemo {
int mi;
int mj;
};
Demo* Demo_Create(int i, int j) {
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL ) {
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
int Demo_Add(Demo* pThis, int value) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
void Demo_Free(Demo* pThis) {
free(pThis);
}//main.c
#include <stdio.h>
#include "50-2.h"
int main() {
Demo* d = Demo_Create(1, 2); // Demo* d = new Demo(1, 2);
printf("d.mi = %d\n", Demo_GetI(d)); // d->getI();
printf("d.mj = %d\n", Demo_GetJ(d)); // d->getJ();
printf("Add(3) = %d\n", Demo_Add(d, 3)); // d->add(3);
// d->mi = 100;
Demo_Free(d);
return 0;
}
小结
- C++中的类对象在内存布局上与结构体相同
- 成员变量和成员函数在内存中分开存放
- 访问权限关键字在运行时失效
- 调用成员时对象地址作为参数隐式传递
51 C++对象模型分析(下)
继承对象模型
-
在C++编译器的内部类可以理解为结构体
-
子类是由父类成员叠加子类新成员得到的
class Derived : public Demo {
int mk;
};
编程实验
-
继承对象模型初探
#include <iostream>
#include <string>
using namespace std;
class Demo {
protected:
int mi;
int mj;
public:
virtual void print() {
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo {
int mk;
public:
Derived(int i, int j, int k) {
mi = i;
mj = j;
mk = k;
}
void print() {
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test {
void* p;
int mi;
int mj;
int mk;
};
int main() {
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
d.print();
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
return 0;
}
多态对象模型
-
C++多态的实现原理
- 当类中声明虚函数时,编译器会在类中生成一个虚函数表
- 虚函数表是一个存储成员函数地址的数据结构
- 虚函数表是由编译器自动生成与维护的
- virtual成员函数会被编译器放入虚函数表中
- 存在虚函数时,每个对象中都有一个指向虚函数表的指针
编程实验
-
多态本质分析
//.h
#ifndef _51_2_H_
#define _51_2_H_
typedef void Demo;
typedef void Derived;
Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);
#endif#include "51-2.h"
#include "malloc.h"
static int Demo_Virtual_Add(Demo* pThis, int value);
static int Derived_Virtual_Add(Demo* pThis, int value);
struct VTable { // 2. 定义虚函数表数据结构
int (*pAdd)(void*, int); // 3. 虚函数表里面存储什么???
};
struct ClassDemo {
struct VTable* vptr; // 1. 定义虚函数表指针 ==》 虚函数表指针类型???
int mi;
int mj;
};
struct ClassDerived {
struct ClassDemo d;
int mk;
};
static struct VTable g_Demo_vtbl = {
Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl = {
Derived_Virtual_Add
};
Demo* Demo_Create(int i, int j) {
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL ) {
ret->vptr = &g_Demo_vtbl; // 4. 关联对象和虚函数表
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_GetI(Demo* pThis) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_GetJ(Demo* pThis) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int value) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + value;
}
// 5. 分析具体的虚函数!!!!
int Demo_Add(Demo* pThis, int value) {
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->vptr->pAdd(pThis, value);
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
Derived* Derived_Create(int i, int j, int k) {
struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
if( ret != NULL ) {
ret->d.vptr = &g_Derived_vtbl;
ret->d.mi = i;
ret->d.mj = j;
ret->mk = k;
}
return ret;
}
int Derived_GetK(Derived* pThis) {
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk;
}
static int Derived_Virtual_Add(Demo* pThis, int value) {
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk + value;
}
int Derived_Add(Derived* pThis, int value) {
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->d.vptr->pAdd(pThis, value);
}//main.cpp
#include "stdio.h"
#include "51-2.h"
void run(Demo* p, int v) {
int r = Demo_Add(p, v);
printf("r = %d\n", r);
}
int main() {
Demo* pb = Demo_Create(1, 2);
Derived* pd = Derived_Create(1, 22, 333);
printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
run(pb, 3);
run(pd, 3);
Demo_Free(pb);
Demo_Free(pd);
return 0;
}
小结
- 继承的本质就是父子成员变量的叠加
- C++中的多态是通过虚函数表实现的
- 虚函数表是由编译器自动生成与维护的
- 虚函数表的调用效率低于普通成员函数
52 C++中的抽象类和接口
什么是抽象类?
- 面向对象中的抽象概念 在进行面向对象分析时,会发现一些抽象的概念!图形的面积如何计算?
在现实中需要知道具体的图形类型才能求面积,所以对概念上的“图形”求面积是没有意义的!
class Shape
{
public:
double area()
{
return 0;
}
};
Shape只是一个概念上的类型,没有具体对象!Shape类有必要存在么吗?
- 面向对象中的抽象类
- 可用于表示现实世界中的抽象概念
- 是一种只能定义类型,而不能产生对象的类
- 只能被继承并且重写相关函数
- 直接特征是相关函数没有完整的实现
- Shape是现实世界中各种图形的抽象概念
- 因此:
- 程序中必须能够反映抽象的图形
- 程序中通过抽象类表示图形的概念
- 抽象类不能创建对象,只能用于继承
抽象类与纯虚函数
- C++语言中没有抽象类的概念
- C++中通过纯虚函数实现抽象类
- 纯虚函数是指只定义原型的成员函数
- 一个C++类中存在纯虚函数就成为了抽象类
- 纯虚函数的语法规则
class Shape
{
public:
virtual double area() = 0;
};
“=0”用于告诉编译器当前是声明纯虚函数,因此不需要定义函数体。
编程实验
- 抽象类初探
#include <iostream>
#include <string>
using namespace std;
class Shape
{
public:
virtual double area() = 0;
};
class Rect : public Shape
{
int ma;
int mb;
public:
Rect(int a, int b)
{
ma = a;
mb = b;
}
double area()
{
return ma * mb;
}
};
class Circle : public Shape
{
int mr;
public:
Circle(int r)
{
mr = r;
}
double area()
{
return 3.14 * mr * mr;
}
};
void area(Shape* p)
{
double r = p->area();
cout << "r = " << r << endl;
}
int main()
{
Rect rect(1, 2);
Circle circle(10);
area(&rect);
area(&circle);
return 0;
}
- 抽象类只能用作父类被继承
- 子类必须实现纯虚函数的具体功能
- 纯虚函数被实现后成为虚函数
- 如果子类没有实现纯虚函数,则子类成为抽象类
- 满足下面条件的C++类则称为接口
- 类中没有定义任何的成员变量
- 所有的成员函数都是公有的
- 所有的成员函数都是纯虚函数
- 接口是一种特殊的抽象类
编程实验
- 接口初探
#include <iostream>
#include <string>
using namespace std;
class Channel
{
public:
virtual bool open() = 0;
virtual void close() = 0;
virtual bool send(char* buf, int len) = 0;
virtual int receive(char* buf, int len) = 0;
};
int main()
{
return 0;
}
小结
- 抽象类用于描述现实世界中的抽象概念
- 抽象类只能被继承不能创建对象
- C++中没有抽象类的概念
- C++中通过纯虚函数实现抽象类
- 类中只存在纯虚函数的称为接口
- 接口是一种特殊的抽象类
53 被遗弃的多重继承(上)
问题
C++中是否允许一个类继承自多个父类?
C++中的多重继承
- C++支持编写多重继承的代码
- 一个子类可以拥有多个父类
- 子类拥有所有父类的成员变量
- 子类继承所有父类的成员函数
- 子类对象可以当作任意父类对象使用
- 多重继承的语法规则
class Derived : public BaseA,public BaseB,public BaseC
{
//......
};
多重继承的本质与单继承相同!
编程实验
- 多重继承问题一
#include <iostream>
#include <string>
using namespace std;
class BaseA
{
int ma;
public:
BaseA(int a)
{
ma = a;
}
int getA()
{
return ma;
}
};
class BaseB
{
int mb;
public:
BaseB(int b)
{
mb = b;
}
int getB()
{
return mb;
}
};
class Derived : public BaseA, public BaseB
{
int mc;
public:
Derived(int a, int b, int c) : BaseA(a), BaseB(b)
{
mc = c;
}
int getC()
{
return mc;
}
void print()
{
cout << "ma = " << getA() << ", "
<< "mb = " << getB() << ", "
<< "mc = " << mc << endl;
}
};
int main()
{
cout << "sizeof(Derived) = " << sizeof(Derived) << endl; // 12
Derived d(1, 2, 3);
d.print();
cout << "d.getA() = " << d.getA() << endl;
cout << "d.getB() = " << d.getB() << endl;
cout << "d.getC() = " << d.getC() << endl;
cout << endl;
BaseA* pa = &d;
BaseB* pb = &d;
cout << "pa->getA() = " << pa->getA() << endl;
cout << "pb->getB() = " << pb->getB() << endl;
cout << endl;
void* paa = pa;
void* pbb = pb;
if( paa == pbb )
{
cout << "Pointer to the same object!" << endl;
}
else
{
cout << "Error" << endl;
}
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "paa = " << paa << endl;
cout << "pbb = " << pbb << endl;
return 0;
}
多重继承的问题一
通过多重继承得到的对象可能拥有“不同的地址”! 解决方案:无
Derived d(1,2,3);
BaseA *pa = &d;
BaseB *pb = &d
多重继承的问题二
- 多重继承可能产生冗余的成员
编程实验
- 多重继承问题二
#include <iostream>
#include <string>
using namespace std;
class People
{
string m_name;
int m_age;
public:
People(string name, int age)
{
m_name = name;
m_age = age;
}
void print()
{
cout << "Name = " << m_name << ", "
<< "Age = " << m_age << endl;
}
};
class Teacher : virtual public People
{
public:
Teacher(string name, int age) : People(name, age)
{
}
};
class Student : virtual public People
{
public:
Student(string name, int age) : People(name, age)
{
}
};
class Doctor : public Teacher, public Student
{
public:
Doctor(string name, int age) : Teacher(name, age), Student(name, age), People(name, age)
{
}
};
int main()
{
Doctor d("Delphi", 33);
d.print();
return 0;
}
当多重继承关系出现闭合时将产生数据冗余的问题! 解决方案:虚继承
class Peploe{ };
class Teacher : virtual public People{ };
class Student : virtual public People{ };
class Doctor : public Teacher,public Student{ };
- 虚继承能够解决数据冗余问题
- 中间层父类不再关心顶层父类的初始化
- 最终子类必须直接调用顶层父类的构造函数
问题:当架构设计中需要继承时,无法确定使用直接继承还是虚继承!
小结
- C++支持多重继承的编程方式
- 多重继承容易带来问题
- 可能出现“同一个对象的地址不同”的情况
- 虚继承可以解决数据冗余的问题
- 虚继承使得架构设计可能出现问题
54 被遗弃的多重继承(下)
多重继承的问题三
- 多重继承可能产生多个虚函数表
编程实验
- 多重继承问题三
#include <iostream>
#include <string>
using namespace std;
class BaseA
{
public:
virtual void funcA()
{
cout << "BaseA::funcA()" << endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout << "BaseB::funcB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
};
int main()
{
Derived d;
BaseA* pa = &d;
BaseB* pb = &d;
BaseB* pbe = (BaseB*)pa; // oops!!
BaseB* pbc = dynamic_cast<BaseB*>(pa);
cout << "sizeof(d) = " << sizeof(d) << endl;
cout << "Using pa to call funcA()..." << endl;
pa->funcA();
cout << "Using pb to call funcB()..." << endl;
pb->funcB();
cout << "Using pbc to call funcB()..." << endl;
pbc->funcB();
cout << endl;
cout << "pa = " << pa << endl;
cout << "pb = " << pb << endl;
cout << "pbe = " << pbe << endl;
cout << "pbc = " << pbc << endl;
return 0;
}
需要进行强制类型转换时,C++中推荐使用新式类型转换关键字! 解决方案:dynamic_cast
Derived d;
BaseA *pa = &d;
BaseB *pb = &d;
BaseB *pbb = (BaseB*)pa;
正确的使用多重继承
- 工程开发中的“多重继承”方式:单继承某个类+实现(多个)接口
编程实验
- 正确的多继承方式
#include <iostream>
#include <string>
using namespace std;
class Base
{
protected:
int mi;
public:
Base(int i)
{
mi = i;
}
int getI()
{
return mi;
}
bool equal(Base* obj)
{
return (this == obj);
}
};
class Interface1
{
public:
virtual void add(int i) = 0;
virtual void minus(int i) = 0;
};
class Interface2
{
public:
virtual void multiply(int i) = 0;
virtual void divide(int i) = 0;
};
class Derived : public Base, public Interface1, public Interface2
{
public:
Derived(int i) : Base(i)
{
}
void add(int i)
{
mi += i;
}
void minus(int i)
{
mi -= i;
}
void multiply(int i)
{
mi *= i;
}
void divide(int i)
{
if( i != 0 )
{
mi /= i;
}
}
};
int main()
{
Derived d(100);
Derived* p = &d;
Interface1* pInt1 = &d;
Interface2* pInt2 = &d;
cout << "p->getI() = " << p->getI() << endl; // 100
pInt1->add(10);
pInt2->divide(11);
pInt1->minus(5);
pInt2->multiply(8);
cout << "p->getI() = " << p->getI() << endl; // 40
cout << endl;
cout << "pInt1 == p : " << p->equal(dynamic_cast<Base*>(pInt1)) << endl;
cout << "pInt2 == p : " << p->equal(dynamic_cast<Base*>(pInt2)) << endl;
return 0;
}
- 一些有用的工程建议
- 先继承自一个父类,然后实现多个接口
- 父类中提供equal()成员函数
- equal()成员函数用于判断指针是否指向当前对象
- 与多重继承相关的强制类型转换用dynamic_cast完成
小结
- 多继承可能出现多个虚函数表指针
- 与多重继承相关的强制类型转换用dynamic_cast完成
- 工程开发中采用"单继承多接口"的方式使用多继承
- 父类提供成员函数用于判断指针是否指向当前对象
55 经典问题解析四
关于动态内存分配
new和malloc的区别是什么?
delete和free的区别是什么?
- new关键字与malloc函数的区别
- new关键字是C++的一部分
- malloc是由C库提供的函数
- new以具体类型为单位进行内存分配
- malloc以字节为单位进行内存分配
- new在申请内存空间时可进行初始化
- malloc仅根据需要申请定量的内存空间
- 下面的代码输出什么?为什么?
class Test
{
public:
Test
{
cout<<"Test"<<endl;
}
};
int main()
{
Test *pn = new Test;
Test *pm = (Test*)malloc(sizeof(Test)); //不自动执行构造函数,只分配空间
return 0;
}
编程实验
- new和malloc的区别
#include <iostream>
#include <string>
#include <cstdlib>
using namespace std;
class Test
{
int* mp;
public:
Test()
{
cout << "Test::Test()" << endl;
mp = new int(100);
cout << *mp << endl;
}
~Test()
{
delete mp;
cout << "~Test::Test()" << endl;
}
};
int main()
{
Test* pn = new Test;
Test* pm = (Test*)malloc(sizeof(Test));
delete pn;
free(pm);
return 0;
}
- new和malloc的区别
- new在所有C++编译器中都被支持
- malloc在某些系统开发中是不能调用
- new能够触发构造函数的调用
- malloc仅分配需要的内存空间
- 对象的创建只能使用new
- malloc不适合面向对象开发
- 下面的代码输出什么?为什么?
int main()
{
Test *pn = new Test;
Test *pm = (Test*)malloc(sizeof(Test));
delete pn;
free(pm); //只释放堆空间,不自动调用析构函数
return 0;
}
- delete和free的区别
- delete在所有C++编译器中都被支持
- free在某些系统开发中是不能调用
- delete能够触发析构函数的调用
- free仅归还之前分配的内存空间
- 对象的销毁只能使用delete
- free不适合面向对象开发
关于虚函数
构造函数是否可以成为虚函数?
析构函数是否可以成为虚函数?
- 构造函数不可能成为虚函数
- 在构造函数执行结束后,虚函数表指针才会被正确的初始化
- 析构函数可以成为虚函数
- 建议在设计类时将析构函数声明为虚函数
编程实验
- 构造,析构,虚函数
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base()" << endl;
func();
}
virtual void func()
{
cout << "Base::func()" << endl;
}
virtual ~Base()
{
func();
cout << "~Base()" << endl;
}
};
class Derived : public Base
{
public:
Derived()
{
cout << "Derived()" << endl;
func();
}
virtual void func()
{
cout << "Derived::func()" << endl;
}
~Derived()
{
func();
cout << "~Derived()" << endl;
}
};
int main()
{
Base* p = new Derived();
// ...
delete p;
return 0;
}
构造函数中是否可以发生多态?
析构函数中是否可以发生多态?
- 构造函数中不可能发生多态行为
- 在构造函数执行时,虚函数表指针未被正确初始化
- 析构函数中不可能发生多态行为
- 在析构函数执行时,虚函数表指针已经被销毁
构造函数和析构函数中不能发生多态行为,只调用当前类中定义的版本!
关于继承中的强制类型转换
继承中如何正确的使用强制类型转换?
- dynamic_cast是与继承相关的类型转换关键字
- dynamic_cast要求相关的类中必须有虚函数
- 用于有直接或者间接继承关系的指针(引用)之间
- 指针:
- 转换成功:得到目标类型的指针
- 转换失败:得到一个空指针
- 引用:
- 转换成功:得到目标类型的引用
- 转换失败:得到一个异常操作信息
- 指针:
- 编译器会检查dynamic_cast的使用是否正确
- 类型转换的结果只可能在运行阶段才能得到
编程实验
- dynamic_cast的使用
#include <iostream>
#include <string>
using namespace std;
class Base
{
public:
Base()
{
cout << "Base::Base()" << endl;
}
virtual ~Base()
{
cout << "Base::~Base()" << endl;
}
};
class Derived : public Base
{
};
int main()
{
Base* p = new Base;
Derived* pd = dynamic_cast<Derived*>(p);
if( pd != NULL )
{
cout << "pd = " << pd << endl;
}
else
{
cout << "Cast error!" << endl;
}
delete p;
return 0;
}
小结
- new/delete会触发构造函数或者析构函数的调用
- 构造函数不能成为虚函数
- 析构函数可以成为虚函数
- 构造函数和析构函数都无法产生多态行为
- dynamic_cast是与继承相关的专用转换关键字